// CGI program to explore the Mandelbrot Set.
//
// Author: David Faden, dfade@gmail.com (2010)

#include "cgi_param.h"

#include <algorithm>
#include <cstdio>
#include <gd.h>
#include <string>
using namespace std;

static const int MAX_ITER = 80;
static const int COLORS[MAX_ITER] = {
  0xFF0000, 0xFF1300, 0xFF2600, 0xFF3900, 0xFF4D00, 0xFF6000, 0xFF7300, 0xFF8600,
  0xFF9900, 0xFFAC00, 0xFFBF00, 0xFFD200, 0xFFE600, 0xFFF900, 0xF2FF00, 0xDFFF00,
  0xCCFF00, 0xB9FF00, 0xA6FF00, 0x93FF00, 0x80FF00, 0x6CFF00, 0x59FF00, 0x46FF00,
  0x33FF00, 0x20FF00, 0x0DFF00, 0x00FF06, 0x00FF1A, 0x00FF2D, 0x00FF40, 0x00FF53,
  0x00FF66, 0x00FF79, 0x00FF8C, 0x00FF9F, 0x00FFB3, 0x00FFC6, 0x00FFD9, 0x00FFEC,
  0x00FFFF, 0x00ECFF, 0x00D9FF, 0x00C6FF, 0x00B2FF, 0x009FFF, 0x008CFF, 0x0079FF,
  0x0066FF, 0x0053FF, 0x0040FF, 0x002DFF, 0x0019FF, 0x0006FF, 0x0D00FF, 0x2000FF,
  0x3300FF, 0x4600FF, 0x5900FF, 0x6C00FF, 0x8000FF, 0x9300FF, 0xA600FF, 0xB900FF,
  0xCC00FF, 0xDF00FF, 0xF200FF, 0xFF00F9, 0xFF00E5, 0xFF00D2, 0xFF00BF, 0xFF00AC,
  0xFF0099, 0xFF0086, 0xFF0073, 0xFF0060, 0xFF004C, 0xFF0039, 0xFF0026, 0xFF0013};
static const int MIN_WIDTH = 20;
static const int MIN_HEIGHT = 20;
static const int MAX_WIDTH = 800;
static const int MAX_HEIGHT = 800;

class TrueColorImage {
public:
  TrueColorImage(int width, int height)
    : image(gdImageCreateTrueColor(width, height)) {}
  ~TrueColorImage() {
    gdImageDestroy(image);
  }
  void setPixel(int x, int y, int color) {
    gdImageSetPixel(image, x, y, color);
  }
  void writePng(FILE* out) {
    gdImagePng(image, out);
  }
private:
  // Hide the copy constructor and assignment operator.
  TrueColorImage(const TrueColorImage&);
  TrueColorImage& operator=(const TrueColorImage&);
  gdImagePtr image;
};

int main()
{
  CgiParam params = CgiParam::fromEnv();
  const int width = min(max(MIN_WIDTH, params.getOne("w", 400)), MAX_WIDTH);
  const int height = min(max(MIN_HEIGHT, params.getOne("h", 400)), MAX_HEIGHT);
  const double top = params.getOne("t", 4.0);
  const double left = params.getOne("l", -4.0);
  const double delta = params.getOne("d", 8.0 / width);
  if (params.has("im")) {
    fputs("Content-type: image/png\r\n\r\n", stdout);
    TrueColorImage img(width, height);
    for (int x = 0; x < width; ++x) {
      for (int y = 0; y < height; ++y) {
	const double a = left + delta * x;
	const double b = top - delta * y;
	int iter = 0;
	double zr = a;
	double zi = b;
	for (;;) {
	  const double zrSq = zr * zr;
	  const double ziSq = zi * zi;
	  if (zrSq + ziSq > 4.0) {
	    break;
	  }
	  ++iter;
	  if (iter >= MAX_ITER) {
	    break;
	  }
	  const double temp = zr;
	  zr = zrSq - ziSq + a;
	  zi = 2 * temp * zi + b;
	}
	img.setPixel(x, y, (iter >= MAX_ITER) ? 0 : COLORS[iter]);
      }
    }
    img.writePng(stdout);
  } else {
    fputs("Content-type: text/html\r\n\r\n", stdout);
    const double newDelta = delta / 2.0;
    const int x = params.getOne("x", width / 2);
    const int y = params.getOne("y", height / 2);
    const double zr = left + x * delta;
    const double zi = top - y * delta;
    const double newLeft = zr - newDelta * (width / 2);
    const double newTop = zi + newDelta * (height / 2);
    fprintf(stdout,
	    "<html><head><title>Mandelbrot Set Explorer</title>"
	    "<meta name=robots content=nofollow></head>"
	    "<body><form><input name=w type=hidden value=%d>"
	    "<input name=h type=hidden value=%d>"
	    "<input name=l type=hidden value=%.20f>"
	    "<input name=t type=hidden value=%.20f>"
	    "<input name=d type=hidden value=%.20f>",
	    width, height, newLeft, newTop, newDelta);
    fprintf(stdout,
	    "<input type=image src=\"mset.cgi?w=%d&h=%d&l=%.20f&t=%.20f&"
            "d=%.20f&im\" width=%d height=%d border=0>"
	    "</form></body></html>",
	    width, height, newLeft, newTop, newDelta, width, height);
  }
}
